using System;
using MessagingWPF.Infrastructure.Utilities;

namespace MessagingWPF.Infrastructure
{
	/// <summary>
	/// A thread-safe subscription manager.
	/// </summary>
	public class SubscriptionManager : IPubSub
	{
		private readonly ISubscriptionAssembler _SubscriptionAssembler;
		private readonly ISubscriptionRegistry _Subscriptions;

		/// <summary>
		/// Initializes a new instance of the class.
		/// </summary>
		/// <param name="subscriptionAssembler">The subscription assembler.</param>
		/// <param name="subscriptions">The subscription registry.</param>
		public SubscriptionManager(
			ISubscriptionAssembler subscriptionAssembler,
			ISubscriptionRegistry subscriptions)
		{
			Ensure.ArgumentIsNotNull(subscriptionAssembler, "subscriptionAssembler");
			Ensure.ArgumentIsNotNull(subscriptions, "subscriptions");

			_SubscriptionAssembler = subscriptionAssembler;
			_Subscriptions = subscriptions;
		}

		/// <summary>
		/// Initializes a new instance of the class.
		/// </summary>
		/// <param name="subscriptionAssembler">The subscription assembler.</param>
		public SubscriptionManager(ISubscriptionAssembler subscriptionAssembler)
			: this(subscriptionAssembler, new SubscriptionRegistry())
		{
		}

		#region IPubSub Members

		/// <summary>
		/// Subscribes the specified subscriber type for messages.
		/// </summary>
		/// <param name="subscriberType">Type of the subscriber.</param>
		/// <exception cref="ArgumentNullException">
		/// Thrown when <paramref name="subscriberType"/> is <c>null</c>.
		/// </exception>
		/// <remarks>This operation is idempotent.</remarks>
		public void Subscribe(Type subscriberType)
		{
			Ensure.ArgumentIsNotNull(subscriberType, "subscriberType");

			var subscriptions = _SubscriptionAssembler.GetAllSubscriptions(subscriberType);

			_Subscriptions.AddSubscriptions(subscriptions);
		}

		/// <summary>
		/// Subscribes the specified subscriber for the specified type of message.
		/// </summary>
		/// <typeparam name="TMessage">
		/// Specifies the type of message the subscriber is interested in.
		/// </typeparam>
		/// <param name="subscriber">The subscriber instance.</param>
		/// <exception cref="ArgumentNullException">
		/// Thrown when <paramref name="subscriber"/> is <c>null</c>.
		/// </exception>
		/// <remarks>This operation is idempotent.</remarks>
		public void Subscribe<TMessage>(ISubscriber<TMessage> subscriber)
		{
			Ensure.ArgumentIsNotNull(subscriber, "subscriber");

			var subscription = _SubscriptionAssembler.GetSingleSubscription(subscriber);

			_Subscriptions.AddSubscription(subscription);
		}

		/// <summary>
		/// Subscribes the specified subscriber for all implemented subscription types.
		/// </summary>
		/// <param name="subscriber">The subscriber.</param>
		/// <exception cref="ArgumentNullException">
		/// Thrown when <paramref name="subscriber"/> is <c>null</c>.
		/// </exception>
		/// <remarks>This operation is idempotent.</remarks>
		public void Subscribe(object subscriber)
		{
			Ensure.ArgumentIsNotNull(subscriber, "subscriber");

			var subscriptions = _SubscriptionAssembler.GetAllSubscriptions(subscriber);

			_Subscriptions.AddSubscriptions(subscriptions);
		}

		/// <summary>
		/// Unsubscribes the specified subscriber type from messages.
		/// </summary>
		/// <param name="subscriberType">Type of the subscriber.</param>
		/// <exception cref="ArgumentNullException">
		/// Thrown when <paramref name="subscriberType"/> is <c>null</c>.
		/// </exception>
		/// <remarks>This operation is idempotent.</remarks>
		public void Unsubscribe(Type subscriberType)
		{
			Ensure.ArgumentIsNotNull(subscriberType, "subscriberType");

			var subscriptions = _SubscriptionAssembler.GetAllSubscriptions(subscriberType);

			_Subscriptions.RemoveSubscriptions(subscriptions);
		}

		/// <summary>
		/// Unsubscribes the specified subscriber from the specified type of message.
		/// </summary>
		/// <typeparam name="TMessage">
		/// Specifies the type of message the subscriber is interested in.</typeparam>
		/// <param name="subscriber">The subscriber instance.</param>
		/// <exception cref="ArgumentNullException">
		/// Thrown when <paramref name="subscriber"/> is <c>null</c>.
		/// </exception>
		public void Unsubscribe<TMessage>(ISubscriber<TMessage> subscriber)
		{
			Ensure.ArgumentIsNotNull(subscriber, "subscriber");

			var subscription = _SubscriptionAssembler.GetSingleSubscription(subscriber);

			_Subscriptions.RemoveSubscription(subscription);
		}

		/// <summary>
		/// Unsubscribes the specified subscriber from all implemented subscription types.
		/// </summary>
		/// <param name="subscriber">The subscriber.</param>
		/// <exception cref="ArgumentNullException">
		/// Thrown when <paramref name="subscriber"/> is <c>null</c>.
		/// </exception>
		/// <remarks>This operation is idempotent.</remarks>
		public void Unsubscribe(object subscriber)
		{
			Ensure.ArgumentIsNotNull(subscriber, "subscriber");

			var subscriptions = _SubscriptionAssembler.GetAllSubscriptions(subscriber);

			_Subscriptions.RemoveSubscriptions(subscriptions);
		}

		/// <summary>
		/// Publishes the specified message to the bus.
		/// </summary>
		/// <param name="message">The message.</param>
		/// <exception cref="ArgumentNullException">
		/// Thrown when <paramref name="message"/> is <c>null</c>.
		/// </exception>
		/// <remarks>The message will only be sent if subscribers for it are present.</remarks>
		public void Publish(object message)
		{
			Ensure.ArgumentIsNotNull(message, "message");

			_Subscriptions.GetSubscriptions(message.GetType())
				.ForEach(subscription => subscription.SendMessage(message));
		}

		#endregion
	}
}